using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;

namespace MT5Trade.Models
{
    /// <summary>
    /// Kelly公式计算核心类
    /// 实现严格的数学计算，不允许经验性近似
    /// 基于Kelly公式：f* = (bp - q)/b
    /// 连续收益情况：f* = μ/σ²
    /// </summary>
    public class KellyCalculator
    {
        #region 常量定义
        /// <summary>
        /// 默认安全系数（保守策略）
        /// </summary>
        public const double DEFAULT_SAFETY_FACTOR = 0.25;

        /// <summary>
        /// 最大安全系数
        /// </summary>
        public const double MAX_SAFETY_FACTOR = 0.5;

        /// <summary>
        /// 破产概率目标阈值（小于5%）
        /// </summary>
        public const double BANKRUPTCY_PROBABILITY_THRESHOLD = 0.05;

        /// <summary>
        /// 计算精度要求（误差小于0.001）
        /// </summary>
        public const double CALCULATION_PRECISION = 0.001;
        #endregion

        #region 核心计算方法

        /// <summary>
        /// 计算胜率（获胜概率）
        /// 基于历史交易数据统计分析
        /// </summary>
        /// <param name="historicalTrades">历史交易记录</param>
        /// <returns>胜率p，范围[0,1]</returns>
        /// <exception cref="ArgumentException">历史数据无效时抛出异常</exception>
        public double CalculateWinRate(IEnumerable<TradeModel> historicalTrades)
        {
            if (historicalTrades == null)
                throw new ArgumentNullException(nameof(historicalTrades), "历史交易数据不能为空");

            var trades = historicalTrades.ToList();
            if (trades.Count == 0)
                throw new ArgumentException("历史交易数据不能为空", nameof(historicalTrades));

            // 计算盈利交易数量
            var profitableTradesCount = trades.Count(trade => 
                (trade.Profit + trade.Swap + trade.Commission + trade.Fee) > 0);

            // 严格按照数学定义计算胜率
            double winRate = (double)profitableTradesCount / trades.Count;

            // 边界条件处理
            if (winRate < 0 || winRate > 1)
                throw new InvalidOperationException($"计算得出的胜率{winRate:F6}超出有效范围[0,1]");

            return winRate;
        }

        /// <summary>
        /// 计算赔率（扣除成本后的净收益比）
        /// b = (平均盈利 - 平均成本) / |平均亏损|
        /// </summary>
        /// <param name="historicalTrades">历史交易记录</param>
        /// <returns>赔率b，正值表示有利赔率</returns>
        /// <exception cref="ArgumentException">历史数据无效时抛出异常</exception>
        public double CalculateOddsRatio(IEnumerable<TradeModel> historicalTrades)
        {
            if (historicalTrades == null)
                throw new ArgumentNullException(nameof(historicalTrades), "历史交易数据不能为空");

            var trades = historicalTrades.ToList();
            if (trades.Count == 0)
                throw new ArgumentException("历史交易数据不能为空", nameof(historicalTrades));

            // 分离盈利和亏损交易
            var profitableTrades = trades.Where(trade => 
                (trade.Profit + trade.Swap + trade.Commission + trade.Fee) > 0).ToList();
            var losingTrades = trades.Where(trade => 
                (trade.Profit + trade.Swap + trade.Commission + trade.Fee) < 0).ToList();

            if (losingTrades.Count == 0)
            {
                // 极端情况：没有亏损交易，返回无限大的近似值
                return double.MaxValue;
            }

            if (profitableTrades.Count == 0)
            {
                // 极端情况：没有盈利交易，赔率为负无穷
                return double.MinValue;
            }

            // 计算平均净盈利（包含所有成本）
            double averageProfit = profitableTrades.Average(trade => 
                trade.Profit + trade.Swap + trade.Commission + trade.Fee);

            // 计算平均净亏损的绝对值
            double averageLoss = Math.Abs(losingTrades.Average(trade => 
                trade.Profit + trade.Swap + trade.Commission + trade.Fee));

            // 防止除零
            if (averageLoss < CALCULATION_PRECISION)
                throw new InvalidOperationException("平均亏损过小，无法计算有效赔率");

            // 严格按照数学定义计算赔率
            double oddsRatio = averageProfit / averageLoss;

            return oddsRatio;
        }

        /// <summary>
        /// 计算破产概率
        /// 使用Gambler's Ruin模型：P = ((q/p)^a - (q/p)^N) / (1 - (q/p)^N)
        /// 其中N为资金上限，a为当前资金
        /// </summary>
        /// <param name="winRate">胜率p</param>
        /// <param name="oddsRatio">赔率b</param>
        /// <param name="currentCapital">当前资金水平</param>
        /// <param name="totalCapital">总资金限额</param>
        /// <returns>破产概率，范围[0,1]</returns>
        public double CalculateBankruptcyProbability(double winRate, double oddsRatio, 
            double currentCapital, double totalCapital)
        {
            // 参数验证
            if (winRate < 0 || winRate > 1)
                throw new ArgumentException($"胜率{winRate:F6}必须在[0,1]范围内", nameof(winRate));
            
            if (currentCapital <= 0)
                throw new ArgumentException("当前资金必须大于0", nameof(currentCapital));
            
            if (totalCapital <= currentCapital)
                throw new ArgumentException("总资金必须大于当前资金", nameof(totalCapital));

            double p = winRate;
            double q = 1 - winRate;
            
            // 处理边界条件
            if (p == 0) return 1.0; // 胜率为0，必然破产
            if (p == 1) return 0.0; // 胜率为1，不会破产
            if (oddsRatio <= 0) return 1.0; // 负赔率，必然破产

            // 公平游戏情况（p = 0.5）
            if (Math.Abs(p - 0.5) < CALCULATION_PRECISION)
            {
                return (totalCapital - currentCapital) / totalCapital;
            }

            // 使用Gambler's Ruin公式
            double qOverP = q / p;
            double a = currentCapital;
            double N = totalCapital;

            try
            {
                double numerator = Math.Pow(qOverP, a) - Math.Pow(qOverP, N);
                double denominator = 1 - Math.Pow(qOverP, N);

                if (Math.Abs(denominator) < CALCULATION_PRECISION)
                    throw new InvalidOperationException("破产概率计算中分母接近零");

                double bankruptcyProbability = numerator / denominator;

                // 确保结果在有效范围内
                bankruptcyProbability = Math.Max(0, Math.Min(1, bankruptcyProbability));

                return bankruptcyProbability;
            }
            catch (OverflowException)
            {
                // 处理数值溢出情况
                if (qOverP > 1)
                    return 1.0; // q > p时，最终会破产
                else
                    return 0.0; // p > q时，破产概率趋向于0
            }
        }

        /// <summary>
        /// 计算Kelly最优仓位比例
        /// 基础Kelly公式：f* = (bp - q)/b
        /// </summary>
        /// <param name="winRate">胜率p</param>
        /// <param name="oddsRatio">赔率b</param>
        /// <returns>最优仓位比例f*</returns>
        public double CalculateOptimalKellyFraction(double winRate, double oddsRatio)
        {
            // 参数验证
            if (winRate < 0 || winRate > 1)
                throw new ArgumentException($"胜率{winRate:F6}必须在[0,1]范围内", nameof(winRate));
            
            if (oddsRatio <= 0)
                throw new ArgumentException("赔率必须大于0", nameof(oddsRatio));

            double p = winRate;
            double q = 1 - winRate;
            double b = oddsRatio;

            // 边界条件处理
            if (p == 0) return 0.0; // 胜率为0，不投资
            if (p == 1) return 1.0; // 胜率为1，全部投资
            
            // 严格按照Kelly公式计算
            double kellyFraction = (b * p - q) / b;

            // 如果Kelly分数为负，表示不应该投资
            return Math.Max(0, kellyFraction);
        }

        /// <summary>
        /// 获取最优仓位大小
        /// 实际手数 = 账户资金 × Kelly分数 × 安全系数
        /// </summary>
        /// <param name="accountBalance">账户余额</param>
        /// <param name="winRate">胜率p</param>
        /// <param name="oddsRatio">赔率b</param>
        /// <param name="safetyFactor">安全系数（默认0.25）</param>
        /// <param name="minPosition">最小仓位（默认0.01手）</param>
        /// <param name="maxPosition">最大仓位限制</param>
        /// <returns>最优仓位大小</returns>
        public double GetOptimalPositionSize(double accountBalance, double winRate, 
            double oddsRatio, double safetyFactor = DEFAULT_SAFETY_FACTOR, 
            double minPosition = 0.01, double maxPosition = double.MaxValue)
        {
            // 参数验证
            if (accountBalance <= 0)
                throw new ArgumentException("账户余额必须大于0", nameof(accountBalance));
            
            if (safetyFactor <= 0 || safetyFactor > MAX_SAFETY_FACTOR)
                throw new ArgumentException($"安全系数必须在(0,{MAX_SAFETY_FACTOR}]范围内", nameof(safetyFactor));
            
            if (minPosition <= 0)
                throw new ArgumentException("最小仓位必须大于0", nameof(minPosition));

            // 计算Kelly最优分数
            double kellyFraction = CalculateOptimalKellyFraction(winRate, oddsRatio);

            // 计算破产概率检查
            double bankruptcyProb = CalculateBankruptcyProbability(winRate, oddsRatio, 
                accountBalance * kellyFraction * safetyFactor, accountBalance);
            
            // 如果破产概率过高，进一步降低安全系数
            if (bankruptcyProb > BANKRUPTCY_PROBABILITY_THRESHOLD)
            {
                safetyFactor *= (BANKRUPTCY_PROBABILITY_THRESHOLD / bankruptcyProb);
                safetyFactor = Math.Max(0.1, safetyFactor); // 确保安全系数不会过小
            }

            // 计算实际仓位大小
            double optimalPosition = accountBalance * kellyFraction * safetyFactor;

            // 应用仓位限制
            optimalPosition = Math.Max(minPosition, Math.Min(maxPosition, optimalPosition));

            return Math.Round(optimalPosition, 2); // 保留两位小数
        }

        /// <summary>
        /// 连续收益情况下的Kelly公式
        /// f* = μ/σ² = 期望收益/方差
        /// </summary>
        /// <param name="expectedReturn">期望收益率μ</param>
        /// <param name="variance">收益率方差σ²</param>
        /// <returns>最优仓位比例</returns>
        public double CalculateContinuousKellyFraction(double expectedReturn, double variance)
        {
            if (variance <= 0)
                throw new ArgumentException("方差必须大于0", nameof(variance));

            // 严格按照连续Kelly公式
            double kellyFraction = expectedReturn / variance;

            // 确保结果为正
            return Math.Max(0, kellyFraction);
        }

        #endregion

        #region 验证和诊断方法

        /// <summary>
        /// 验证计算结果的数学正确性
        /// </summary>
        /// <param name="winRate">胜率</param>
        /// <param name="oddsRatio">赔率</param>
        /// <param name="kellyFraction">Kelly分数</param>
        /// <returns>验证结果</returns>
        public bool ValidateCalculationResults(double winRate, double oddsRatio, double kellyFraction)
        {
            try
            {
                // 验证输入参数范围
                if (winRate < 0 || winRate > 1) return false;
                if (oddsRatio <= 0) return false;
                if (kellyFraction < 0) return false;

                // 验证Kelly公式计算精度
                double expectedKelly = CalculateOptimalKellyFraction(winRate, oddsRatio);
                double calculationError = Math.Abs(kellyFraction - expectedKelly);
                
                return calculationError < CALCULATION_PRECISION;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// 获取极端情况处理建议
        /// </summary>
        /// <param name="winRate">胜率</param>
        /// <param name="oddsRatio">赔率</param>
        /// <returns>处理建议</returns>
        public string GetExtremeConditionAdvice(double winRate, double oddsRatio)
        {
            if (winRate <= 0)
                return "胜率为0或负数，建议停止交易或使用最小仓位";
            
            if (winRate >= 1)
                return "胜率为1，建议限制最大仓位防止过度自信";
            
            if (oddsRatio <= 0)
                return "负赔率，不建议进行交易";
            
            if (winRate < 0.3 && oddsRatio < 1.5)
                return "胜率和赔率都较低，建议谨慎交易";
            
            return "参数正常，可以按Kelly公式进行仓位计算";
        }

        #endregion
    }
}
